package com.pronosticos.reportes.dao.impl;

import java.io.Serializable;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.googlecode.genericdao.dao.hibernate.GeneralDAOImpl;
import com.googlecode.genericdao.search.Filter;
import com.googlecode.genericdao.search.Search;
import com.googlecode.genericdao.search.Sort;
import com.pronosticos.reportes.dao.GenericDao;

@Transactional
@SuppressWarnings({ "rawtypes", "unchecked" })
public class GenericDaoImpl<T, ID extends Serializable> extends GeneralDAOImpl
implements GenericDao<T, ID> {

    private Class<T> clazz;

    protected SessionFactory sessionFactory;

    protected GenericDaoImpl(final Class<T> clazz) {
        this.clazz = clazz;
    }

    public GenericDaoImpl(final Class<T> clazz,
            final SessionFactory sessionFactory) {
        this(clazz);
        setSessionFactory(sessionFactory);
    }

    @Override
    public void delete(final T object) {
        final Session session = sessionFactory.getCurrentSession();
        session.delete(object);
    }

    /**
     * ejecuta el query original mas las opciones adicionales (filtros,
     * ordenamiento, paginacion)
     *
     * @param original
     * @param opciones
     * @return
     */

    protected List execute(final Search original, final Search opciones) {
        if (opciones == null) {
            return search(original);
        } else {
            merge(original, opciones);
            if (requierePaginar(original)) {
                return new SearchResultListWrapper(searchAndCount(original),
                        opciones.getMaxResults(), opciones.getPage());
            }
            return search(original);
        }
    }

    @Override
    public List<T> findAll() {
        return findAll((Search) null);
    }

    @Override
    public List<T> findAll(final Search search) {
        final Search query = new Search(this.clazz);
        return execute(query, search);
    }

    @Override
    public T findById(final ID id) {
        final Session session = sessionFactory.getCurrentSession();
        final Object o = session.get(clazz, id);
        return o != null ? clazz.cast(o) : null;
    }

    /**
     * Adiciona filtros, ordenamiento y paginacion al search original.
     *
     * @param original
     * @param opciones
     */
    private void merge(final Search original, final Search opciones) {
        // filtros
        if (!opciones.getFilters().isEmpty()) {
            original.addFilterAnd(opciones.getFilters().toArray(
                    new Filter[opciones.getFilters().size()]));
        }

        // ordenamiento
        if (!opciones.getSorts().isEmpty()) {
            original.addSorts(opciones.getSorts().toArray(
                    new Sort[opciones.getSorts().size()]));
        }

        // fetchs
        if (!opciones.getFetches().isEmpty()) {
            original.addFetches(opciones.getFetches().toArray(
                    new String[opciones.getFetches().size()]));
        }

        // informacion de paginacion
        original.setFirstResult(opciones.getFirstResult());
        original.setMaxResults(opciones.getMaxResults());
        original.setPage(opciones.getPage());
    }

    @Override
    public void refresh(final T object) {
        final Session session = sessionFactory.getCurrentSession();
        session.refresh(object);
    }

    private boolean requierePaginar(final Search opciones) {
        return opciones.getMaxResults() > 0 || opciones.getPage() > 0
                || opciones.getFirstResult() > 0;
    }

    @Autowired
    @Override
    public void setSessionFactory(final SessionFactory sessionFactory) {
        super.setSessionFactory(sessionFactory);
        this.sessionFactory = sessionFactory;
    }

    @Override
    public void store(final T object) {
        final Session session = sessionFactory.getCurrentSession();
        session.saveOrUpdate(object);
    }

}
